package Twitim::MainWindow;

use strict;
use warnings;
use utf8;

use Gtk2::Pango;
use Glib qw(TRUE FALSE);

use Twitim::L10N;
use Twitim::Twitim;
use Twitim::HyperText;
use Twitim::Preferences;
use Twitim::XMPP;
use Twitim::NetBase;

my @Watchlist;
my %Buddy;
my %Keyword;
my %Tw_Msg_Id;
my $Spell_is_ok;
my $CFG;
my $Watchlist_Menu;

my %Color = (
    time  => { light => "#555",    dark => "#888" },
    url   => { light => "blue",    dark => "#436eee" },
    match => { light => "red",     dark => "red" },
    mark  => { light => "#ee0000", dark => "#ab0000" },
);

binmode( STDOUT, ':utf8' );

sub new {
    my $this = shift;
    $CFG = shift;
    my $class = ref($this) || $this;
    my $self = { view_menu => 1, top_visible => 1, @_ };
    bless $self, $class;

    @Watchlist   = get_watchlist($CFG);
    %Buddy       = _get_searchword( $CFG, 'buddy' );
    %Keyword     = _get_searchword( $CFG, 'keyword' );
    $Spell_is_ok = 1 if ( eval 'use Gtk2::Spell; 1;' ? TRUE : FALSE );
    $self->_setup_gui();

    return $self;
}

sub quit {
    my ( $self, $session_disconnect ) = @_;

    if ( $CFG->{'default'}->{'use_geometry'} && !$session_disconnect ) {
        $self->_save_geometry;
    }

    if ( $self->{'view_menu'} == 0 ) {
        $CFG->{'default'}->{'hide_menubar'} = 1;
    }
    else {
        delete $CFG->{'default'}->{'hide_menubar'};
    }

    if ( $self->{'action'}->get_action('View-status')->get_active == 0 ) {
        $CFG->{'default'}->{'hide_statusbar'} = 1;
    }
    else {
        delete $CFG->{'default'}->{'hide_statusbar'};
    }

    &main::quit($session_disconnect);
}

sub reset_text_view {
    my $self = shift;

    # delete text view
    foreach my $tabname ( $self->get_opened_tabs ) {
        my $buf  = $self->{"$tabname"}->get_buffer;
        $buf->delete($buf->get_start_iter, $buf->get_end_iter);
        $self->_set_tab_label_widget( $tabname, FALSE );
    }

    # delete msg_id
    for my $key ( keys %Tw_Msg_Id ) {
        delete $Tw_Msg_Id{$key};
    }

    # main::webapi_connect();
}

sub hide_show_window {
    my $self = shift;

    if ( $self->{'top_visible'} ) {
        $self->_save_geometry;
        $self->{'nb'}->{'current_page'} = $self->{'nb'}->get_current_page;
        $self->{'top'}->hide_all;
    }
    else {
        $self->_set_geometry(1);
        $self->{'top'}->show_all;
        $self->{'nb'}->set_current_page( $self->{'nb'}->{'current_page'} );
        undef $self->{'nb'}->{'current_page'};
    }

    $self->{'top_visible'} = not $self->{'top_visible'};

    return TRUE;
}

sub set_menu_sensitive {
    my ( $self, $item, $state ) = @_;
    $self->{'action'}->get_action($item)->set_sensitive($state);
}

sub set_urgency_hint {
    my ( $self, $state ) = @_;
    $self->{'top'}->set_urgency_hint($state);
}

sub get_active_watchlist {
    my $self = shift;
    return \@Watchlist;
}

sub get_pickup_keywords {
    my ( $self, $tabname ) = @_;
    return $Keyword{$tabname};
}

sub insert_reply_name {
    my ( $self, $username, $id ) = @_;
    my $buf = $self->{log_send}->get_buffer;

    if ($id) {
        $self->{'reply_id'} = $id;
        $self->{'reply_to'} = $username;
    }

    $buf->insert_at_cursor("\@$username ");
}

sub insert_dm_name {
    my ( $self, $username ) = @_;
    my $buf = $self->{log_send}->get_buffer;
    $buf->insert( $buf->get_iter_at_line(0), "d $username " );
}

sub insert_retweet {
    my ( $self, $username, $body ) = @_;
    my $buf = $self->{'log_send'}->get_buffer;
    $buf->insert_at_cursor("RT \@$username: $body");
    $buf->place_cursor( $buf->get_iter_at_line(0) );
}

sub retweet {
    my ( $widget, $id ) = @_;
    $main::TWITTER->retweet($id);
}

sub buffer_auto_scroll {
    my ( $self, $name, $force, $textview ) = @_;
    $textview ||= $self->{$name};
    my $buf      = $textview->get_buffer;

    my $reverse = $CFG->{'default'}->{'reverse_timeline_order'};
    #print "-- $Twitim::HyperText::Hovering_Over_Link\n";

    if ( defined $force ) {
        undef $Twitim::HyperText::Force_Auto_Scroll;
    }
    else {
        my ( $u, $p, $v ) = _get_adjustment($textview);
        my $scrollbar_pos = $reverse ? $v :  $u - $p - $v;

        if ( $u - $p - $v > 10 ) {
            return;
        }
        elsif ($Twitim::HyperText::Hovering_Over_Link) {
            my $tabname = $self->get_current_tab_name;

            if ( $tabname eq $name ) {
                $Twitim::HyperText::Force_Auto_Scroll ||= $name;

                # print "$Twitim::HyperText::Force_Auto_Scroll\n";
                return;
            }
        }
    }

    my $iter = $reverse ? $buf->get_start_iter : $buf->get_end_iter;
    my $yalign = $reverse ? 1.0 : 0.0;
    my $end_mark = $buf->create_mark( 'end', $iter, FALSE );
    $textview->scroll_to_mark( $end_mark, 0.0, TRUE, 0.0, $yalign );
}

sub append_mesg {
    my ( $self, $jid, $body, $id, $timestr, $output, $rt ) = @_;

    Gtk2->main_iteration while ( Gtk2->events_pending );

    # surpress redundant character strings.
    $body =~ s/\n\n+/\n\n/g;
    $body =~ s/((.)\2{8,10})/$1 /g;

    my $state = _check_obsolete_message($id);

    # for only each tab
    if ( $output ne 'all but Timeline' || $output eq 'all' ) {
        my $tab = ( $output eq 'all' ) ? 'Timeline' : $output;
        $state = TRUE if $output ne 'Replies' && $output ne 'Direct Messages';
        $jid =~ s{^Direct from }{} if $tab eq "Direct Messages";
        $self->_decoration_mesg( $tab, $jid, $body, $timestr, $id, $state, $rt);
    }

    # for all but timeline tab
    if ( $output eq 'all but Timeline' || $output eq 'all' ) {
        my $notification;
        my $my_jid = Twitim::NetBase->get_my_id;
        my @opend_watchlist = $self->_get_orderd_opened_tabs;

        foreach
            my $name ( grep !/^Timeline$/, get_watchlist( $CFG, 1 ) )
        {
            my ( $buddy_ok, $keyword_ok ) = ( 0, 0 );
            $buddy_ok = 1 if ( defined $Buddy{$name} && $Buddy{$name} ne '' );
            $keyword_ok = 1
                if ( defined $Keyword{$name} && $Keyword{$name} ne '' );

            if (   ( $buddy_ok   && $jid  =~ /^($Buddy{$name})$/ )
                or ( $buddy_ok   && $body =~ /\@($Buddy{$name})/ )
                or ( $keyword_ok && $body =~ /($Keyword{$name})/i )
                or ( $name eq "Replies" && $jid eq $my_jid && $body =~ /\@/ )
                or ( $name eq "Direct Messages" && $jid =~ /^Direct from / )
                or (   $name eq "Direct Messages"
                    && $jid eq $my_jid
                    && $body =~ /^\s?d [a-zA-Z0-9_]+/ )
                )
            {
                $notification ||= $name if $jid ne $my_jid;
                $jid =~ s{^Direct from }{} if $name eq "Direct Messages";
                $self->_decoration_mesg( $name, $jid, $body, $timestr, $id,
                    $state, $rt ) if grep /$name/, @opend_watchlist;
            }
        }

        if ($notification) {
            $main::NOTIFY->notify( $notification, $output, $jid, $body, $id );
        }
    }

    return;
}

sub set_status_line {
    my ( $self, $context_desc, $text ) = @_;

    return if ( defined $text and $text =~ /Rate limit exceeded/ );

    my $statusbar  = $self->{'statusbar'};
    my $context_id = $statusbar->get_context_id($context_desc);

    $statusbar->pop($context_id);
    $statusbar->push( $context_id, $text ) if defined $text;
}

sub get_current_tab_name {
    my $self = shift;
    my $num  = $self->{'nb'}->get_current_page;

    return '' if $num < 0;

    my $child   = $self->{'nb'}->get_nth_page($num);
    my $tabname = $self->{'nb'}->get_tab_label($child)->{'name'};

    return $tabname;
}

sub change_font {
    my ( $self, $font ) = @_;

    $font = $self->{'font'}->{'system'} unless defined $font;

    foreach my $name ( keys %{ $self->{'label'} }, 'log_send' ) {
        my $textview = $self->{$name};
        my ( $u, $p, $v ) = _get_adjustment($textview);

        $textview->modify_font(
            Gtk2::Pango::FontDescription->from_string($font) );

        $self->buffer_auto_scroll( $name, TRUE )
            if ( $name ne 'log_send' && $u - $p - $v < 10 );
    }
}

# Show links info on statusbar
#
sub show_link_info {
    my ( $self, $hovering_over_link, $text_view, $x, $y, $tag, $tagname )
        = @_;

    if ($hovering_over_link) {
        my $iter = $text_view->get_iter_at_location( $x, $y );
        my $text = get_tagged_text( $text_view, $iter, $tag );

        while ( $text =~ /^ *$/ ) {
            $iter->forward_char;
            $text = get_tagged_text( $text_view, $iter, $tag );
        }

        if ( $tagname =~ /uname/ ) {
            $text =~ s{Direct from }{};
            $text =~ tr/(): //d;
            $text = Twitim::NetBase->get_username($text);
        }

        $self->set_status_line( 'linkinfo', $text );
    }
    else {
        $self->set_status_line('linkinfo');
    }
}

sub get_watchlist {
    my ( $config, $include_un_use ) = @_;
    my ( @result, %tmp_list );
    my $cfg_block = [ grep !/^default$/, sort keys %$config ];

    foreach my $key (@$cfg_block) {
        if (   !exists $CFG->{$key}->{'in_use'}
            || $CFG->{$key}->{'in_use'}
            || $include_un_use )
        {
            $tmp_list{$key}
                = exists $CFG->{$key}->{'sort_key'}
                ? $CFG->{$key}->{'sort_key'}
                : $key;
        }
    }

    @result = sort keys %tmp_list;
    @result = sort { $tmp_list{$a} cmp $tmp_list{$b} || $a cmp $b }
        keys %tmp_list;
    return @result;
}

sub check_spell_is_ok {
    return $Spell_is_ok;
}

sub enable_check_spell {
    my ( $self, $enable ) = @_;
    my $textview = $self->{'log_send'};

    return if !$self->check_spell_is_ok;

    my $spell = Gtk2::Spell->get_from_text_view($textview);

    if ($enable) {
        $spell = Gtk2::Spell->new_attach($textview) unless $spell;
    }
    else {
        $spell->detach if $spell;
    }
}

sub get_opened_tabs {
    my $self = shift;
    my @opened_tabs = keys %{ $self->{'label'} };
    return @opened_tabs;
}

###########################################################################
# output messages

sub _decoration_mesg {
    my ( $self, $name, $jid, $body, $time, $id, $status, $rt ) = @_;
    my $buf        = $self->{"$name"}->get_buffer;
    my $is_start   = $buf->get_end_iter->is_start;
    my $reverse = $CFG->{'default'}->{'reverse_timeline_order'};

    $self->_set_tab_label_widget( $name, $status );
    $time = Twitim::NetBase->get_formatted_time($time);
    $time = "($time) ";
    $time = "\n" . $time if !$is_start and !$reverse;
    $time = $time . "\n" if $reverse;

    my $weight = PANGO_WEIGHT_NORMAL;
    my $color = $Color{'time'}{ $self->{'color_mode'}};
    $color = '' if $reverse;

    if ( $jid =~ /^Direct from / ) {
        $weight = PANGO_WEIGHT_BOLD;
        $color = '';
    }

    _append_text( $buf, $time, 'time', $color, $weight );
    $self->_decoration_jid( $name, $jid, $id, $rt );

    $self->_decoration_body( $name, " " . $body );
    $self->buffer_auto_scroll($name);

    $self->{'action'}->get_action("Mark All")->set_sensitive(TRUE)
        unless $self->{'action'}->get_action("Mark All")->get_sensitive;
}

sub _decoration_jid {
    my ( $self, $name, $jid, $id, $rt ) = @_;
    my $buf = $self->{"$name"}->get_buffer;
    my $reverse = $CFG->{'default'}->{'reverse_timeline_order'};

    my $iter = $reverse ? _move_iter_first_line_end($buf) : $buf->get_end_iter;
    my $mark = $buf->create_mark( undef, $iter, 1 );

    if ( $CFG->{'default'}->{'show_user_icon'} ) {
        ( my $icon_jid = $jid ) =~ s{Direct from }{};
        $self->_insert_user_icon( $icon_jid, $buf );
    }

    if ($rt) {
        my $custom_icon = '/etc/twitim/retweet.png';
        my $system_icon
            = '/usr/share/icons/gnome/32x32/actions/gtk-refresh.png';

        my $image_fullpath = -e $custom_icon ? $custom_icon : $system_icon;
        $self->_insert_icon( $buf, $image_fullpath, 1 ) if -e $image_fullpath;
    }

    my $append = _append_text( $buf, $jid . ":", undef,
        Twitim::NetBase->get_color( $jid, $self->{'color_mode'} ),
        PANGO_WEIGHT_BOLD );

    if ( $jid =~ /^(Direct from )?\(?[a-zA-Z0-9_]+\)?$/ ) {
        my $iter = $buf->get_end_iter;
        my $line = $iter->get_line;
        $line-- if $reverse; # reverse mode inserts '\n' at the end of lines.
        $Tw_Msg_Id{"$name"}[$line] = $id;

        $iter = $reverse ? $buf->get_start_iter : $buf->get_end_iter;
        $iter->forward_to_line_end if $reverse;

        $buf->apply_tag_by_name( 'uname', $buf->get_iter_at_mark($mark),
            $iter ) if $append;
    }
}

sub _move_iter_first_line_end {
    my $buf = shift;
    my $iter = $buf->get_start_iter;
    $iter->forward_to_line_end;
    return $iter
}

sub _insert_user_icon {
    my ( $self, $jid, $buf ) = @_;

    $jid =~ tr/()//d;

    return unless Twitim::NetBase->icon_is_ok($jid);
    my $image_fullpath = Twitim::NetBase->get_icon($jid);
    return if ( !$image_fullpath || !-e $image_fullpath || !defined $buf );

    $self->_insert_icon($buf, $image_fullpath, 
                        $CFG->{'default'}->{'icon_size'});
}

sub _insert_icon {
    my ( $self, $buf, $image_fullpath, $size_multiple ) = @_;

    my $iter
        = $CFG->{'default'}->{'reverse_timeline_order'}
        ? _move_iter_first_line_end($buf)
        : $buf->get_end_iter;

    my $image = Gtk2::Gdk::Pixbuf->new_from_file($image_fullpath);
    my $dpi   = Gtk2::Gdk::Screen->get_default->get_resolution;

    my $w = $image->get_width;
    my $h = $image->get_height;

    my $font_size = $self->_get_current_font_size * ( $dpi / 72 );
    my $size_h    = int( $font_size * $size_multiple + 0.5 );
    my $size_w    = int( $size_h * $w / $h );
    # print "$size_w, $size\n";

    $image = $image->scale_simple( $size_w, $size_h, 'bilinear' );
    $buf->insert_pixbuf( $iter, $image );
    _append_text( $buf, " " );
}

sub _decoration_body {
    my ( $self, $name, $body ) = @_;
    my $buf = $self->{$name}->get_buffer;

    my $re_url  = q{s?https?://[-_.!~*'a-zA-Z0-9;/?:@&=+$,%#]+};
    my $re_hash = q{\B\#[a-zA-Z0-9_]+};
    my $re_user = q{\B@[a-zA-Z0-9_]+};
    my $re_dm   = q{\sd\s[a-zA-Z0-9_]+};
    my $re      = "$re_url|$re_hash|$re_user|$re_dm";
    $re .= '|' . $Keyword{$name} if $Keyword{$name};

    pos $body = 0;

    while ( pos $body < length $body ) {

        if ( $body =~ m{ \G ($re_url) }gcxmso ) {
            _append_text( $buf, $1, 'url' );
        }
        elsif ( $body =~ m{ \G ($re_hash) }gcxmso ) {
            _append_text( $buf, $1, 'url' );
        }
        elsif ( $body =~ m{ \G ($re_user) }gcxmso ) {
            ( my $user = $1 ) =~ tr/@//d;
            _append_text( $buf, '@' );
            _append_text( $buf, $user, 'url' );
        }
        elsif ( $body =~ m{ \G ($re_dm) }gcxmso ) {
            ( my $user = $1 ) =~ s{\s*d\s*}{};
            _append_text( $buf, ' d ' );
            _append_text( $buf, $user, 'url' );
        }
        elsif ( $Keyword{$name} && $body =~ m{ \G ($Keyword{$name}) }gcxmsi ) {
            _append_text( $buf, $1, 'match' );
        }
        else {
            $body =~ m{ \G ( (?: (?!$re). )* ) }gcxmsi;
            last if !defined $1;
            _append_text( $buf, $1 );
        }
    }
}

sub _append_text {
    my ( $buf, $txt, $tag, $color, $weight ) = @_;

    $tag ||= ''; # for time strings

    if ( !defined $txt ) {
        print "oops!\n";
        $buf->insert_with_tags_by_name( $buf->get_end_iter, "oops", "uname" );
        return 0;
    }

    my $reverse = $CFG->{'default'}->{'reverse_timeline_order'};
    my $iter = $reverse ? $buf->get_start_iter : $buf->get_end_iter;
    $iter->forward_to_line_end if $reverse && $tag ne 'time' && $tag ne 'mark';
    $tag = undef if $tag eq 'time';

    if ( $tag ) {
        $buf->insert_with_tags_by_name( $iter, $txt, $tag );
    }
    else {
        $weight ||= PANGO_WEIGHT_NORMAL;

        my @text_props = ( "weight", $weight );
        splice @text_props, 0, 0, ( "foreground", $color ) if $color;

        $buf->insert_with_tags( $iter, $txt, $buf->create_tag(undef, @text_props) );

    }

    return 1;
}

sub _check_obsolete_message {
    my $id = shift;

    return TRUE if !defined $id;

    my $target = 'max_message_id';
    $target = 'max_dm_id' if $id =~ s{^d_}{};

    my $max
        = exists $CFG->{'default'}->{$target}
        ? $CFG->{'default'}->{$target}
        : 0;

    my $status = $id > $max ? TRUE : FALSE;
    # print "$id, $max: $status\n";

    if ( $target eq 'max_dm_id' && $CFG->{'default'}->{'max_dm_id'} < $id ) {
        $CFG->{'default'}->{'max_dm_id'} = $id;
    }

    return $status;
}

###########################################################################
# process signals

sub _send_mesg {
    my ( $widget, $event, $self ) = @_;

    return
        if ( $event->keyval != $Gtk2::Gdk::Keysyms{'Return'}
        && $event->keyval != $Gtk2::Gdk::Keysyms{'KP_Enter'} );

    return
        if ( $CFG->{'default'}->{'send_with_ctrl'}
        && not( $event->state >= "control-mask" ) );

    return TRUE if $event->type ne 'key-release';

    my $sendbuf = $widget->get_buffer;
    my ( $start, $end ) = $sendbuf->get_bounds;
    my $string = $sendbuf->get_text( $start, $end, 1 );
    chomp($string);
    return if $string =~ /^[ \n]*$/;

    my $unixtime = time;
    main::send_message( $string, $self->{'reply_id'} );
    undef $self->{'reply_id'};

    # delete send box
    $sendbuf->delete( $start, $end );
    $sendbuf->place_cursor($start);

    # regular expression for Twitter commands
    my ( $output, @tmp );
    for my $ref (Twitim::XMPP->get_cmd_array) {
        push @tmp, @{$ref}[0];
    }
    my $twitter_command = join( '|', @tmp );

    $output
        = ( $string =~ /^ *($twitter_command)/ )
        ? $self->get_current_tab_name
        : 'all';

    # $self->buffer_auto_scroll('Timeline', 1);
    # print "$string: $output\n";
    $self->append_mesg( Twitim::NetBase->get_my_id,
        $string, undef, $unixtime, $output );
}

sub _popup_menu {
    my ( $text_view, $event, $self ) = @_;
    my $tagname;
    my $string;

    return 0 unless $event->button == 3;

    my ( $x, $y )
        = $text_view->window_to_buffer_coords( 'widget', $event->x,
        $event->y );
    my $iter = $text_view->get_iter_at_location( $x, $y );
    my $line = $iter->get_line;

    foreach my $tag ( $iter->get_tags ) {
        $tagname = $tag->get_property('name');
        if ( defined $tagname ) {
            $string = get_tagged_text( $text_view, $iter, $tag );
            last if $tagname eq 'uname' or $tagname eq 'url';
        }
    }

    $tagname ||= '';
    return 0 if $tagname eq '';    # Display default popup menu.

    if ( $event->type eq 'button-release' ) {
        my $menu;

        if ( $string !~ /http:/ ) {
            $string =~ s/Direct from //;
            $string =~ tr/(): //d;
            my $body = _get_body_at_line($text_view, $line);
            $menu = $self->_make_popup_user_menu( $string, $tagname, $line,
                $body );
        }
        else {
            $menu = _make_popup_url_menu($string);
        }

        $menu->popup( undef, undef, undef, undef, $event->button,
            $event->time );
    }
    return 1;
}

sub _get_body_at_line {
    my ( $text_view, $line ) = @_;
    my $buffer = $text_view->get_buffer;

    my $start = $buffer->get_iter_at_line($line);
    my $end = $buffer->get_iter_at_line($line);
    $end->forward_to_line_end;

    my $body = $buffer->get_text( $start, $end, 1 );
    $body =~ s{\([0-9:-]+\) +[0-9A-Za-z_]+: +}{};
    # print $body,"\n";
    return $body;
}

sub _add_menu_receive_popup {
    my ( $textview, $menu, $self ) = @_;
    my $buf = $textview->get_buffer;
    my @menuitem;

    $menuitem[0] = Gtk2::CheckMenuItem->new( l10n("Show _Menubar") );
    $menuitem[1] = Gtk2::SeparatorMenuItem->new();
    $menuitem[0]->set_active( $self->{'view_menu'} );
    $menuitem[0]->signal_connect(
        toggled => sub {
            $self->{'view_menu'} = $menuitem[0]->get_active;
            my $action = $self->{'action'}->get_action('View-menu');
            $action->set_active( $self->{'view_menu'} );
            $self->_show_widget( 'menubar', $self->{'view_menu'} );
        }
    );

    $menu->insert( $menuitem[1], 7 );
    $menu->insert( $menuitem[0], 7 );

    my $search_engine_name = 'Google';
    my $search_engine_url  = 'http://www.google.com/search?q=';

    if ( exists $CFG->{'default'}->{'search_engine'} ) {
        my @search_engine
            = split( /,\s/, $CFG->{'default'}->{'search_engine'} );
        ( $search_engine_name, $search_engine_url ) = @search_engine;
    }

    if ( $buf->get_has_selection ) {
        my ( $start, $end ) = $buf->get_selection_bounds;
        my $string = $buf->get_text( $start, $end, 0 );

        $menuitem[2] = Gtk2::SeparatorMenuItem->new();
        $menuitem[3] = Gtk2::MenuItem->new(
            sprintf( l10n("_Search %s"), $search_engine_name ) );
        $menuitem[3]->signal_connect(
            activate => sub {
                open_url(qq{"$search_engine_url$string"});
            }
        );

        $menu->prepend( $menuitem[2] );
        $menu->prepend( $menuitem[3] );
    }

    for ( my $i = 0; $i <= $#menuitem; $i++ ) {
        $menuitem[$i]->show();
    }
}

sub add_menu_send_popup {
    my ( $textview, $menu, $self ) = @_;
    my $menu_twitter = Gtk2::Menu->new();
    my %menuitem;

    foreach my $ref (Twitim::XMPP->get_cmd_array) {
        my $itemname = @{$ref}[0];

        $menuitem{$itemname} = Gtk2::MenuItem->new( l10n( @{$ref}[1] ) );
        $menuitem{$itemname}->signal_connect(
            activate => sub { $self->_insert_command($itemname); } );
        $menu_twitter->append( $menuitem{$itemname} );

        if ( $itemname =~ /^(whois|off)$/ ) {
            my $menuitem = Gtk2::SeparatorMenuItem->new();
            $menu_twitter->append($menuitem);
        }
    }

    my $separate_menuitem = Gtk2::SeparatorMenuItem->new();
    my $root_menuitem
        = Gtk2::MenuItem->new( l10n('Insert Twitter _Command') );
    $root_menuitem->set_submenu($menu_twitter);
    $root_menuitem->set_sensitive(FALSE) 
        if !$main::XMPP->is_valid || defined $main::XMPP->get_error;

    $menu->insert( $separate_menuitem, 7 );
    $menu->insert( $root_menuitem,     7 );

    $separate_menuitem->show;
    $root_menuitem->show_all;
}

sub _check_tab {
    my ( $self, $nb, $event, $page ) = @_;

    return FALSE if ( ( ref $event ) =~ /Button/ && $event->button == 3 );
    return FALSE if defined $self->{'nb'}->{'current_page'};

    my $tabname = $self->get_current_tab_name();    # get previous page
    $self->_set_tab_label_widget( $tabname, FALSE ) if $tabname;

    if ( defined $page ) {
        if ( $page == 0 ) {
            $self->{'action'}->get_action("Close-Tab")->set_sensitive(FALSE);
        }
        else {
            $self->{'action'}->get_action("Close-Tab")->set_sensitive(TRUE);
        }
    }

    foreach my $name ( grep !/Timeline/, keys %{ $self->{'label'} } ) {
        return 0
            if ( $self->{'label'}->{$name}->{'notified'}
            && exists $CFG->{$name}
            && $CFG->{$name}->{'notification'} );
    }

    $self->set_urgency_hint(FALSE);
    return 0;
}

sub _do_menuitem {
    my ( $widget, $userdata ) = @_;
    my ( $command, $username, $self, $id )
        = ( $userdata->[0], $userdata->[1], $userdata->[2], $userdata->[3] );
    my $buf = $self->{log_send}->get_buffer;

    my $rt_sub
        = $CFG->{'default'}->{'unofficial_retweet'}
        ? sub { $self->insert_retweet( $username, $id ); } # id = body
        : sub { $self->retweet($id); };

    my %dispatch = (
        whois => sub {
            main::send_message("whois $username");
            $self->buffer_auto_scroll( 'Timeline', 1 );
        },
        open => sub { open_url( $main::TWITTER_URL . $username ); },
        read => sub {
            my $tabname = $self->get_current_tab_name;
            $main::TWITTER->user_timeline( $username, $tabname, 3 );
        },
        d     => sub { $self->insert_dm_name($username); },
        reply => sub { $self->insert_reply_name( $username, $id ); },
        rt    => $rt_sub,
    );

    $dispatch{$command}->();
}

sub _set_favourings {
    my ( $widget, $id ) = @_;

    if ( $widget->get_active ) {
        $main::TWITTER->create_favourings($id);
    }
    else {
        $main::TWITTER->destroy_favourings($id);
    }
}

sub _insert_command {
    my ( $self, $action, $action_name ) = @_;
    my $buf = $self->{log_send}->get_buffer;

    if ( $action->isa('Gtk2::Action') ) {
        $action_name = $action->get_name;
        $action_name =~ s/TW-(.*)/$1/;
    }
    else {
        $action_name = $action;
    }

    $buf->insert( $buf->get_iter_at_line(0), $action_name . " " );
    my $iter = $buf->get_iter_at_offset( length($action_name) + 1 );
    $buf->place_cursor($iter);
}

sub _zoom_font_size {
    my ( $self, $action ) = @_;
    my $action_name = $action->get_name;
    my $font_size   = $self->_get_current_font_size;

    if ( $action_name eq 'Zoom-in' ) {
        $font_size *= 1.1;
    }
    elsif ( $action_name eq 'Zoom-out' ) {
        $font_size /= 1.1;
    }
    else {
        my $font      = $self->_get_default_font;
        my $font_desc = Gtk2::Pango::FontDescription->from_string($font);
        $font_size = $font_desc->get_size / PANGO_SCALE;
    }

    # print "$font_size\n";
    $self->{'font'}->{'size'} = $font_size;
    $self->change_font($font_size);
}

sub _set_view {
    my ( $self, $action ) = @_;

    #    my $action = shift;
    my $target;

    my $name   = $action->get_name;
    my $status = $action->get_active;

    if ( $name eq 'View-menu' ) {
        $self->{'view_menu'} = $status;
        $target = 'menubar';
    }
    elsif ( $name eq 'View-status' ) {
        $target = 'statusbar';
    }

    $self->_show_widget( $target, $status );
}

sub _count_chars {
    my ( $buf, $self ) = @_;
    my $c            = $buf->get_char_count;
    my $context_desc = 'count_char';

    if ( $c > 0 ) {
        if ( $self->{'reply_id'} ) {
            $self->set_status_line( $context_desc,
                sprintf(
                    l10n("%d characters (Reply to %s)"),
                    $c, $self->{'reply_to'}
                )
            );
        }
        else {
            $self->set_status_line( $context_desc,
                sprintf( l10n("%d characters"), $c ) );
        }
    }
    else {
        $self->set_status_line($context_desc);
        undef $self->{'reply_id'} if $self->{'reply_id'};
    }
}

sub _set_mark {
    my ( $self, $action ) = @_;
    my $action_name = $action->get_name;

    my $mark_format
        = $CFG->{'default'}->{'reverse_timeline_order'} ? " %s \n" : "\n %s ";
    my $text = sprintf( $mark_format, l10n("- Mark -") );
    my @tab;

    if ( $action_name eq 'Mark' ) {
        @tab = ( $self->get_current_tab_name() );
    }
    else {
        @tab = keys %{ $self->{'label'} };
    }

    foreach my $tabname (@tab) {
        my $buf  = $self->{"$tabname"}->get_buffer;
        my $iter = $buf->get_end_iter;
        my $line = $iter->get_line;
        my $id   = $Tw_Msg_Id{"$tabname"}[$line] || 'UNDEF';

        unless ( $id eq 'mark' || $iter->is_start ) {
            $self->{'action'}->get_action("Mark All")->set_sensitive(FALSE)
                if ( $action_name eq 'Mark All' );
            _append_text( $buf, $text, 'mark' );

            $line = $buf->get_end_iter->get_line;
            $Tw_Msg_Id{"$tabname"}[$line] = 'mark';
            $self->buffer_auto_scroll( $tabname, 1 );
        }
    }
}

sub _open_tab {
    my ( $self, $action, $tab_name ) = @_;
    my ( $api, $option );

    if ( ( ref $action ) =~ /Action/ ) {
        $tab_name = $action->get_name;
        $api      = $main::TWITTER->name2api($tab_name);
    }
    else {
        $tab_name         = $action;
        $Buddy{$tab_name} = $tab_name;
        $api              = 'user_timeline';
        $option           = $tab_name;
    }

    $Buddy{$tab_name}   ||= "";
    $Keyword{$tab_name} ||= "";

    unless ( grep /$tab_name/, keys %{ $self->{'label'} } ) {
        $self->_make_scrolled_window($tab_name);
    }

    $main::TWITTER->$api($tab_name, $option) if defined $api;
}

sub _close_tab {
    my ( $self, $tabname ) = @_;

    $tabname = $self->get_current_tab_name() if ( ref $tabname ) =~ /Action/;

    return if $tabname eq 'Timeline';

    $self->{"sw_$tabname"}->destroy;
    $self->{$tabname}->destroy;
    $self->{'label'}->{"$tabname"}->destroy;

    delete $self->{"sw_$tabname"};
    delete $self->{$tabname};
    delete $self->{'label'}->{"$tabname"};

    $self->_set_open_tab_menu_sensitive( $tabname, TRUE );
}

sub _restore_tab_layout {
    my $self = shift;

    # delete
    foreach my $tabname ( keys %{ $self->{'label'} } ) {
        $self->_close_tab($tabname);
    }

    # open
    foreach my $tabname (@Watchlist) {
        $self->_make_scrolled_window($tabname);
    }

    main::webapi_connect(TRUE);
}

sub _scroll_all_tab {
    my $self = shift;

    foreach my $name ( keys %{ $self->{'label'} } ) {
        my $textview = $self->{$name};
        $self->buffer_auto_scroll( $name, TRUE );
    }
}

sub _get_orderd_opened_tabs {
    my $self = shift;

    my $n_pages = $self->{'nb'}->get_n_pages;
    my @opend_tabs;

    foreach my $page_num ( 0 .. $n_pages - 1 ) {
        my $scroll_window = $self->{'nb'}->get_nth_page($page_num);
        my $hbox          = $self->{'nb'}->get_tab_label($scroll_window);
        push @opend_tabs, $hbox->{'name'};
    }

    return @opend_tabs;
}

###########################################################################
# make GUI

sub _setup_gui {
    my $self = shift;

    # top window
    my $w = $self->{'top'} = Gtk2::Window->new('toplevel');

    $w->set_title($main::APP_NAME);

    # $w->set_opacity(0.5);
    $w->set_icon_from_file($main::APP_ICON) if $main::APP_ICON;
    $w->stick if $CFG->{'default'}->{'sticky'};
    $w->set_skip_taskbar_hint( $CFG->{'default'}->{'skip_taskbar'} )
        if $CFG->{'default'}->{'skip_taskbar'};

    $w->signal_connect(
        focus_in_event => sub { $self->_save_geometry; return FALSE; } );
    $w->signal_connect(
        delete_event => sub {
            if ( $main::TRAY_ICON && $main::TRAY_ICON->is_embedded ) {
                $self->hide_show_window;
            }
            else {
                $self->quit;
            }
        }
    );
    $w->signal_connect(
        window_state_event => sub {
            my ( $widget, $event ) = @_;
            my $state
                = $event->new_window_state =~ /iconified/ ? FALSE : TRUE;
            $widget->set_skip_taskbar_hint($state) 
                if $CFG->{'default'}->{'skip_taskbar'};
            return TRUE;
        }
    );

    $w->add( my $vb = $self->{'vbox'} = Gtk2::VBox->new( FALSE, 0 ) );

    my $context = $w->get_pango_context;
    $self->{'font'}->{'system'} = $context->get_font_description->to_string;
    $self->{'color_mode'} = check_color_mode($w);

    # menubar
    $self->_make_menubar($vb);

    # container
    $vb->pack_start( my $vp = $self->{'vp'} = Gtk2::VPaned->new, 1, 1, 0 );
    $vp->set_border_width(5);
    $vp->pack1( my $nb = $self->{'nb'} = Gtk2::Notebook->new, 1, 0 );

    $nb->popup_enable;
    $nb->set_scrollable(1);
    $nb->signal_connect( button_press_event => sub { $self->_check_tab } );
    $nb->signal_connect( switch_page        => sub { $self->_check_tab } );

    # recieve box
    $self->_make_scrolled_window('Timeline');

    foreach my $name (@Watchlist) {
        $self->_make_scrolled_window($name);
    }

    # send box
    $vp->pack2( my $sw2 = $self->{log_send_sb} = Gtk2::ScrolledWindow->new,
        0, 1 );
    $sw2->set_policy( 'automatic', 'automatic' );
    $sw2->set_shadow_type('etched-in');
    $sw2->add( my $log2 = $self->{log_send} = Gtk2::TextView->new );
    $log2->modify_base( 'normal', Gtk2::Gdk::Color->parse('#eee') )
        if exists $CFG->{'default'}->{'twitter_jid'};
    $log2->signal_connect( key_release_event => \&_send_mesg, $self );
    $log2->signal_connect( key_press_event   => \&_send_mesg, $self );
    $log2->get_buffer->signal_connect( changed => \&_count_chars, $self );
    $log2->signal_connect( populate_popup => \&add_menu_send_popup, $self );
    $self->_set_textview_properties($log2);
    $nb->signal_connect( button_release_event => sub { $w->set_focus($log2); }
    );
    $self->enable_check_spell( $CFG->{'default'}->{'spell_check'} );

    # statusbar
    $vb->pack_start( $self->{'statusbar'} = Gtk2::Statusbar->new, 0, 0, 0 );

    if (   $CFG->{'default'}->{'use_geometry'}
        && $CFG->{'default'}->{'geometry'} )
    {
        $self->_set_geometry;
    }
    else {
        $w->set_default_size( 380, 500 );
    }

    # view
    if ( exists $CFG->{'default'}->{'hide_menubar'} ) {
        $self->{'view_menu'} = 0;
        $self->_show_widget( 'menubar', 0 );
    } 

    if ( exists $CFG->{'default'}->{'hide_statusbar'} ) {
        $self->{'action'}->get_action('View-status')->set_active(FALSE);
        $self->_show_widget( 'statusbar', 0 );
    }

    $w->set_focus($log2);
    $w->show_all;
}

sub _make_scrolled_window {
    my ( $self, $name ) = @_;
    my ( %sw, %log, $cb_parameter );

    $self->{'nb'}->append_page( $sw{$name} = $self->{"sw_$name"}
            = Gtk2::ScrolledWindow->new,
        l10n($name) );

    $self->_set_tab_label_widget($name);

    $sw{$name}->set_policy( 'automatic', 'always' );
    $sw{$name}->set_shadow_type('etched-in');
    $sw{$name}->add( $log{$name} = $self->{$name} = Gtk2::TextView->new );

    $log{$name}->set_editable(FALSE);
    $log{$name}->set_cursor_visible(FALSE);
    $log{$name}->signal_connect( event_after => \&event_after );
    $log{$name}->signal_connect(
        motion_notify_event => sub {
            my ( $text_view, $event ) = @_;
            $self->motion_notify_event( $text_view, $event );
        }
    );
    $log{$name}->signal_connect(
        visibility_notify_event => sub {
            my ( $text_view, $event ) = @_;
            $self->visibility_notify_event( $text_view, $event );
        }
    );
    $log{$name}->signal_connect(
        populate_popup => \&_add_menu_receive_popup,
        $self
    );
    $log{$name}->get_buffer->create_tag(
        'url',
        foreground => $Color{'url'}{ $self->{'color_mode'} },
        underline  => 'single'
    );
    $log{$name}
        ->get_buffer->create_tag( 'uname', weight => PANGO_WEIGHT_BOLD );
    $log{$name}->get_buffer->create_tag(
        'match',
        weight     => PANGO_WEIGHT_NORMAL,
        foreground => $Color{'match'}{ $self->{'color_mode'} }
    );
    $log{$name}->get_buffer->create_tag(
        'mark',
        weight        => PANGO_WEIGHT_BOLD,
        foreground    => 'white',
        background    => $Color{'mark'}{ $self->{'color_mode'} },
        justification => 'center'
    );
    $log{$name}
        ->signal_connect( button_press_event => \&_popup_menu, $self );
    $log{$name}
        ->signal_connect( button_release_event => \&_popup_menu, $self );

    $self->_set_textview_properties( $log{$name} );
    $self->_set_open_tab_menu_sensitive( $name, FALSE );

    $sw{$name}->show_all;
}

sub _set_textview_properties {
    my ( $self, $tv ) = @_;

    $tv->set_wrap_mode('word-char');
    $tv->set_left_margin(3);
    $tv->set_right_margin(3);
    $tv->set_pixels_above_lines(1);
    $tv->set_pixels_below_lines(5);
    $tv->set_pixels_inside_wrap(2);

    my $font      = $self->_get_default_font;
    my $font_desc = Gtk2::Pango::FontDescription->from_string($font);
    $font_desc->set_size( $self->{'font'}->{'size'} * PANGO_SCALE )
        if exists $self->{'font'}->{'size'};
    $tv->modify_font($font_desc);
}

sub _set_tab_label_widget {
    my ( $self, $name, $status ) = @_;
    my @box;

    my $imagefile = $main::PIXMAPS_DIR . "invisible.png";
    $imagefile = $main::PIXMAPS_DIR . "available.png" if $status;

    foreach my $i ( 0 .. 1 ) {
        $box[$i] = Gtk2::HBox->new;

        my $img = Gtk2::Image->new_from_file($imagefile);
        $box[$i]->pack_start( $img, 0, 0, 0 );

        my $label = Gtk2::Label->new();
        $label->set_label( l10n($name) );

        $box[$i]->pack_start( $label, 0, 0, 0 );
        $box[$i]->show_all();
    }

    $box[0]->{'name'}           = $name;
    $box[0]->{'notified'}       = $status;
    $self->{'label'}->{"$name"} = $box[0];

    #    print "$self->{'nb'}\n";

    $self->{'nb'}->set_tab_label( $self->{"sw_$name"}, $box[0] );
    $self->{'nb'}->set_menu_label( $self->{"sw_$name"}, $box[1] );
}

sub _call_preferences {
    my $self = shift;

    if ( $self->{'preferences'} ) {
        $self->{'preferences'}->present;
        return;
    }

    $self->{'preferences'} = Twitim::Preferences->new(
        on_delete => sub {
            @Watchlist = $self->{'preferences'}->save();
            $self->{'preferences'}->destroy();
            delete $self->{'preferences'};
        },
        on_destroy => sub {
            $self->{'preferences'}->quit();
            $self->_update_open_tab_menu_and_pickups
        },
        on_destroy_tab_prop => sub {
            @Watchlist = $self->{'preferences'}->save();
            $self->_update_open_tab_menu_and_pickups;
            ( $self->{'old_actions'}, undef ) = $self->_make_watchlist_menu_entries();
        },
    );

    ( $self->{'old_actions'}, undef ) = $self->_make_watchlist_menu_entries();
    $self->{'preferences'}->start( $CFG, $self );
}

###########################################################################
# menu

sub _update_open_tab_menu_and_pickups {
    my $self = shift;

    # delete old menu
    $self->{'uimanager'}->remove_ui($Watchlist_Menu);

    foreach my $name ( @{ $self->{'old_actions'} } ) {
        my $action = $self->{'action'}->get_action( $name->[0] );
        $self->{'action'}->remove_action($action);
    }

    # add new menu
    my ( $wl_actions, $wl_ui ) = $self->_make_watchlist_menu_entries();
    if ( @$wl_actions > 0 ) {
        $self->{'action'}->add_actions( $wl_actions, undef );
        $Watchlist_Menu = $self->{'uimanager'}->add_ui_from_string($wl_ui);
    }

    # set sensitive
    foreach my $name ( _get_custom_watchlist() ) {
        my $state
            = ( grep /^$name$/, keys %{ $self->{'label'} } )
            ? FALSE
            : TRUE;
        $self->{'action'}->get_action($name)->set_sensitive($state);
    }

    %Buddy   = _get_searchword( $CFG, 'buddy' );
    %Keyword = _get_searchword( $CFG, 'keyword' );
}

sub _make_menubar {
    my ( $self, $vbox ) = @_;

    my @entries = (

        # File menus
        [ "FileMenu", undef, l10n("_File") ],
        [   "Connect", 'gtk-connect', l10n("XMPP _Reconnect"), undef, undef,
            sub { $main::XMPP->connect }
        ],
        [ "Open-Tab", undef, l10n("_Open Tab") ],
        [   "Restore-Tab", undef, l10n("_Restore Default Tab Layout"),
            undef, undef, sub { $self->_restore_tab_layout; }
        ],
        [   "Close-Tab", 'gtk-close', l10n("_Close Tab"), "<control>W", undef,
            sub { my $action = shift; $self->_close_tab($action); }
        ],
        [ "Quit", 'gtk-quit', undef, "<control>Q", undef, sub { $self->quit } ],

        # Edit menus
        [ "Edit", undef, l10n("_Edit") ],
        [   "Mark All", undef, l10n('Insert _Mark'), "<alt>M", undef,
            sub { my $action = shift; $self->_set_mark($action); }
        ],
        [ "Twitter", undef, l10n('Insert Twitter _Command') ],
        [   "Scroll Bottom", 'gtk-goto-bottom',
            l10n("_Scroll All Tabs to the Bottom"),
            "<alt>End", undef, sub { $self->_scroll_all_tab; }
        ],
        [   "Scroll Top", 'gtk-goto-top',
            l10n("_Scroll All Tabs to the Top"),
            "<alt>Home", undef, sub { $self->_scroll_all_tab; }
        ],
        [   "Preferences", 'gtk-preferences',
            undef,         undef,
            undef, sub { $self->_call_preferences }
        ],

        # View menus
        [ "View", undef, l10n("_View") ],
        [   "Zoom-in", 'gtk-zoom-in',
            undef,     "<control>plus",
            undef,     sub { my $act = shift; $self->_zoom_font_size($act); }
        ],
        [   "Zoom-out", 'gtk-zoom-out',
            undef,      "<control>minus",
            undef,      sub { my $act = shift; $self->_zoom_font_size($act); }
        ],
        [   "Zoom-100", 'gtk-zoom-100', undef, "<control>0", undef,
            sub { my $act = shift; $self->_zoom_font_size($act); }
        ],

        # Help menus
        [ "Help", undef, l10n("_Help") ],
        [   "Online-Help", 'gtk-help', undef, "F1", undef,
            sub { open_url( l10n($main::HELP_URL) ); }
        ],
        [ "About", 'gtk-about', undef, undef, undef, sub { $self->_show_about } ]
    );

    my @toggle = (

        #View menus
        [   "View-menu", undef, l10n("_Menubar"), undef, undef,
            sub { my $act = shift; $self->_set_view($act); },
            $self->{'view_menu'}
        ],
        [   "View-status", undef, l10n("_Statusbar"), undef, undef,
            sub { my $act = shift; $self->_set_view($act); }, TRUE
        ]
    );

    # Added dynamic menu entries
    my ( $wl_actions, $wl_ui ) = $self->_make_watchlist_menu_entries;

    my ( $tw_api_actions, $tw_api_ui ) = $self->_make_twitter_api_menu_entries;
    push @entries, @$tw_api_actions;

    my ( $tw_cmd_actions, $tw_cmd_ui ) = $self->_make_twitter_cmd_menu_entries;
    push @entries, @$tw_cmd_actions;

    my $tw_scroll_all
        = $CFG->{'default'}->{'reverse_timeline_order'} ? "Top" : "Bottom";

    my $ui_basic = "
<ui>
  <menubar name='MenuBar'>
    <menu action='FileMenu'> 
      <menuitem action='Connect' />
      <separator/>
      <menu action='Open-Tab'>
        $tw_api_ui
        <separator/>
        <placeholder name='watchlist'>
        </placeholder>
      </menu>
      <menuitem action='Restore-Tab'/>
      <menuitem action='Close-Tab'/>
      <separator/>
      <menuitem action='Quit'/>
    </menu>
    <menu action='Edit'> 
      <menuitem action='Mark All' />
      <menu action='Twitter'> 
      $tw_cmd_ui
      </menu>
      <separator/>
      <menuitem action='Scroll $tw_scroll_all' />
      <separator/>
      <menuitem action='Preferences' />
    </menu>
    <menu action='View'> 
      <menuitem action='View-menu' />
      <menuitem action='View-status' />
      <separator/>
      <menuitem action='Zoom-in' />
      <menuitem action='Zoom-out' />
      <menuitem action='Zoom-100' />
    </menu>
    <menu action='Help'> 
      <menuitem action='Online-Help' />
      <menuitem action='About' />
    </menu>
  </menubar>
</ui>";

    my $uimanager = $self->{'uimanager'} = Gtk2::UIManager->new;

    my $accelgroup = $uimanager->get_accel_group;
    $self->{'top'}->add_accel_group($accelgroup);

    my $actions = $self->{'action'} = Gtk2::ActionGroup->new("actions_basic");
    $actions->add_actions( \@entries, undef );
    $actions->add_actions( $wl_actions, undef ) if @$wl_actions > 0 ;
    $actions->add_toggle_actions( \@toggle, undef );
    $actions->get_action("Mark All")->set_sensitive(FALSE);

    $uimanager->insert_action_group( $actions, 0 );
    $uimanager->add_ui_from_string($ui_basic);
    $Watchlist_Menu = $uimanager->add_ui_from_string($wl_ui);    # FIXME
    my $menubar = $self->{'menubar'} = $uimanager->get_widget('/MenuBar');
    $vbox->pack_start( $menubar, FALSE, FALSE, 0 );

    # menu tray icon
    my $menuicon = $self->{'menuicon'} = Gtk2::MenuItem->new();
    $menuicon->set_right_justified(TRUE);
    $menuicon->signal_connect(
        button_press_event => sub {
            my ( $w, $e ) = @_;
            print $e->type . "\n";
            return TRUE;
        }
    );

    $self->set_menu_sensitive( "Connect", 0 );
    $self->set_menu_sensitive( "Twitter", 0 );
    $menubar->append($menuicon);
}

sub _set_open_tab_menu_sensitive {
    my ( $self, $tabname, $state ) = @_;
    my @all_watchlist = get_watchlist( $CFG, 1 );
    my @tablist = @all_watchlist;

    # all watch list menus
    push( @tablist, grep( !/Timeline/, Twitim::Twitter->get_api_names ), @all_watchlist );
    $self->{'action'}->get_action("$tabname")->set_sensitive($state)
        if grep /$tabname/, @tablist;
}

sub _set_menu_icon {
    my ( $self, $icon, $tip ) = @_;

    if ( $self->{'menuimg'} ) {
        $self->{'menuimg'}->destroy;
        delete $self->{'menuimg'};
    }

    return unless defined $icon;

    my $imagefile = $main::PIXMAPS_DIR . $icon;
    my $img = $self->{'menuimg'} = Gtk2::Image->new_from_file($imagefile);
    $img->set_tooltip_markup($tip);

    $self->{'menuicon'}->add($img);
    $self->{'menuicon'}->show_all;
}

sub _make_watchlist_menu_entries {
    my $self = shift;
    my ( @entries, $ui, $ui_item );

    foreach my $name ( _get_custom_watchlist() ) {
        push @entries,
            [
            "$name", undef, $name, undef, undef,
            sub { my $action = shift; $self->_open_tab($action) }
            ];
        $ui_item .= "<menuitem action='$name' />\n";
    }

    $ui_item ||= "";

    $ui = "
<ui>
<menubar name='MenuBar'>
  <menu action='FileMenu'> 
  <menu action='Open-Tab'>
  <placeholder name='watchlist'>
  $ui_item
  </placeholder>
  </menu>
  </menu>
</menubar>
</ui>";

    return ( \@entries, $ui );
}

sub _make_twitter_api_menu_entries {
    my $self = shift;
    my ( @entries, $ui );

    foreach my $array (Twitim::Twitter->get_api_array) {
        next if $array->[0] eq 'Timeline';

        push @entries,
            [
            "$array->[0]", undef, l10n( $array->[1] ),
            undef, undef, sub { my $action = shift; $self->_open_tab($action) }
            ];
        $ui .= "<menuitem action=\"$array->[0]\" />\n";
    }

    return ( \@entries, $ui );
}

sub _make_twitter_cmd_menu_entries {
    my $self = shift;
    my ( @entries, $ui );

    foreach my $ref (Twitim::XMPP->get_cmd_array) {
        push @entries,
            [
            "TW-@{$ref}[0]", undef, l10n( @{$ref}[1] ),
            undef, undef,
            sub { my $act = shift; $self->_insert_command($act); }
            ];
        $ui .= "<menuitem action=\"TW-@{$ref}[0]\" />\n";
        $ui .= "<separator/>\n" if ( @{$ref}[0] =~ /^(whois|off)$/ );
    }

    return ( \@entries, $ui );
}

sub _make_popup_user_menu {
    my ( $self, $twname, $tagname, $line, $body ) = @_;
    my $menu = Gtk2::Menu->new();
    my @menuitem;

    my $use_twitter = TRUE;
    unless ( exists $CFG->{'default'}->{'twitter_user'} )
    {
        $use_twitter = FALSE;
    }

    my $tabname = $self->get_current_tab_name();
    my $all_num = $#{$Tw_Msg_Id{$tabname}};
    $line = $all_num - $line if $CFG->{'default'}->{'reverse_timeline_order'};
    my $id      = $Tw_Msg_Id{$tabname}[$line];

    if ( $tagname eq 'uname' ) {
        $menuitem[0] = Gtk2::MenuItem->new( l10n("_Reply to This Message") );
        $menuitem[0]->signal_connect(
            activate => \&_do_menuitem,
            [ "reply", $twname, $self, $id ]
            );

        $menuitem[1] = Gtk2::MenuItem->new( l10n("R_etweet This Message") );
        my $rt_arg = $CFG->{'default'}->{'unofficial_retweet'} ? $body : $id;
        $menuitem[1]->signal_connect(
            activate => \&_do_menuitem, [ "rt", $twname, $self, $rt_arg ] );
    }
    else {
        $menuitem[0] = Gtk2::MenuItem->new( l10n("_Reply") );
        $menuitem[0]->signal_connect(
            activate => \&_do_menuitem,
            [ "reply", $twname, $self ]
            );
        $menuitem[1] = ""
    }

    $menuitem[2] = Gtk2::MenuItem->new( l10n("_Direct Message") );
    $menuitem[2]->set_sensitive(0) if !Twitim::NetBase->check_follower($twname);
    $menuitem[3] = Gtk2::SeparatorMenuItem->new();

    $menuitem[4] = Gtk2::MenuItem->new( l10n("_Read Recent Messages") );
    $menuitem[5]
        = Gtk2::MenuItem->new( l10n("Read Recent Messages in Other _Tab") );
    $menuitem[6] = Gtk2::SeparatorMenuItem->new();

    $menuitem[7] = Gtk2::MenuItem->new( l10n("_Open User Page in Browser") );
    $menuitem[8] = Gtk2::ImageMenuItem->new( l10n("_Whois") );
    $menuitem[8]
        ->set_image( Gtk2::Image->new_from_stock( "gtk-info", 'menu' ) );

    $menuitem[2]->signal_connect(
        activate => \&_do_menuitem,
        [ "d", $twname, $self ]
    );
    $menuitem[4]->signal_connect(
        activate => \&_do_menuitem,
        [ "read", $twname, $self ]
    );
    $menuitem[5]
        ->signal_connect( activate => sub { $self->_open_tab($twname) } );
    $menuitem[7]->signal_connect(
        activate => \&_do_menuitem,
        [ "open", $twname, $self ]
    );
    $menuitem[8]->signal_connect(
        activate => \&_do_menuitem,
        [ "whois", $twname, $self ]
    );

    if ( $tagname eq 'uname' ) {
        $menuitem[9] = Gtk2::SeparatorMenuItem->new();
        $menuitem[10] = Gtk2::CheckMenuItem->new( l10n("_Favorite") );

        if (  !defined $id
            || defined $main::TWITTER->get_error
            || $use_twitter == FALSE
            || $id =~ /^d_/ )
        {
            $menuitem[10]->set_sensitive(FALSE);
        }
        elsif ( Twitim::NetBase->get_favorites($id) ) {
            $menuitem[10]->set_active(TRUE);
        }

        $menuitem[10]->signal_connect( toggled => \&_set_favourings, $id );
    }

    if ( defined $main::TWITTER->get_error || $use_twitter == FALSE ) {
        $menuitem[4]->set_sensitive(FALSE);
        $menuitem[5]->set_sensitive(FALSE);
    }

    $menuitem[8]->set_sensitive(FALSE) if defined $main::XMPP->get_error;

    for ( my $i = 0; $i <= $#menuitem; $i++ ) {
        next if !$menuitem[$i];
        $menuitem[$i]->show();
        $menu->append( $menuitem[$i] );
    }

    return $menu;
}

sub _make_popup_url_menu {
    my $url  = shift;
    my $menu = Gtk2::Menu->new();

    my $menuitem0 = Gtk2::MenuItem->new( l10n("_Open Link in Browser") );
    my $menuitem1 = Gtk2::ImageMenuItem->new( l10n("_Copy Link Location") );
    $menuitem1->set_image(
        Gtk2::Image->new_from_stock( "gtk-copy", 'menu' ) );

    $menuitem0->signal_connect( activate => sub { open_url($url) } );
    $menuitem1->signal_connect(
        activate => sub {
            Gtk2::Clipboard->get( Gtk2::Gdk->SELECTION_CLIPBOARD )
                ->set_text($url);
        }
    );

    $menuitem0->show();
    $menuitem1->show();
    $menu->append($menuitem0);
    $menu->append($menuitem1);

    return $menu;
}

###########################################################################
# other widgets

sub _show_about {
    my $self = shift;

    my $about = Gtk2::AboutDialog->new;

    Gtk2::AboutDialog->set_url_hook( sub { open_url($main::APP_URL) } );
    $about->set_transient_for( $self->{'top'} );

    $about->set_program_name($main::APP_NAME);
    $about->set_icon_from_file($main::APP_ICON) if $main::APP_ICON;
    $about->set_authors('Yoshizumi Endo <y-endo@ceres.dti.ne.jp>');
    $about->set_artists('Hylke Bons <h.bons@student.rug.nl>');

    $about->set_logo(
        Gtk2::Gdk::Pixbuf->new_from_file('/etc/twitim/twitim-large.png') )
        if ( -e '/etc/twitim/twitim-large.png' );
    # $about->set_license("GPL 2");
    $about->set_website($main::APP_URL);
    $about->set_comments( l10n('A Twitter Client for the GNOME Desktop') );
    $about->set_copyright("Copyright © 2008-2012 Yoshizumi Endo.");
    $about->set_version($main::VERSION);

    $about->run;
    $about->destroy;
}

sub _show_widget {
    my ( $self, $name, $status ) = @_;

    if ($status) {
        $self->{$name}->show_all;
    }
    else {
        $self->{$name}->hide_all;
    }
}

###########################################################################
# misc

sub _get_adjustment {
    my $textview = shift;

    my $adjustment = $textview->get_parent->get_vadjustment;
    my $u          = $adjustment->get('upper');
    my $p          = $adjustment->get('page_size');
    my $v          = $adjustment->get('value');

    return ( $u, $p, $v );
}

sub _get_default_font {
    my $self = shift;

    my $system_font = $CFG->{'default'}->{'system_font'};
    my $font        = $self->{'font'}->{'system'};
    $font = $CFG->{'default'}->{'font'} if $system_font == 0;
    return $font;
}

sub _get_current_font_size {
    my ( $self, $textview )  = @_;
    $textview ||= $self->{'Timeline'};
    my $style     = $textview->get_style;
    my $font_size = $style->font_desc()->get_size / PANGO_SCALE;
    return $font_size;
}

sub _save_geometry {
    my $self = shift;

    return if $self->{'top'}->window->get_state =~ /maximized/;

    my ( $root_x, $root_y ) = $self->{'top'}->get_position;
    my ( $width,  $height ) = $self->{'top'}->get_size;
    my $paned_position = $self->{'vp'}->get_position;

    $CFG->{'default'}->{'geometry'}
        = "$root_x, $root_y, $width, $height, $paned_position";
}

sub _set_geometry {
    my ( $self, $without_paned ) = @_;

    return
        if $self->{'top'}->window
            && $self->{'top'}->window->get_state =~ /maximized/;

    my @geo = split( /,\s/, $CFG->{'default'}->{'geometry'} );

    $self->{'top'}->move( $geo[0], $geo[1] );
    $self->{'top'}->set_default_size( $geo[2], $geo[3] );
    $self->{'vp'}->set_position( $geo[4] ) if !$without_paned;
}

###########################################################################
# watchlist

sub _get_custom_watchlist {
    my $twitter_tablist = join( "|", Twitim::Twitter->get_api_names );
    my @tablist = grep !/^$twitter_tablist$/, get_watchlist( $CFG, 1 );

    return @tablist;
}

sub _get_searchword {
    my ( $cfg, $target ) = @_;
    my %searchword;

    my @watchlist = get_watchlist( $cfg, 1 );
    my $my_id = Twitim::NetBase->get_my_id;

    foreach my $name (@watchlist) {
        if ( $name eq 'Replies' && $my_id ) {
            $searchword{'Replies'} = "\@$my_id\\b";
        }
        elsif ( exists $cfg->{$name}->{$target} ) {
            my @config = split( /\s*,\s*/, $cfg->{$name}->{$target} );
            my @tmp;
            foreach my $content (@config) {
                push @tmp, quotemeta($content);
            }
            $searchword{$name} = join( "|", @tmp );
        }
        else {
            $searchword{$name} = "";
        }
    }

    return %searchword;
}

sub check_color_mode {
    my $widget = shift;

    my $base  = Gtk2::Rc->get_style($widget)->base('normal')->to_string;
    my $color = Gtk2::Gdk::Color->parse($base);
    my ( $red, $green, $blue )
        = map { $_ / 255 } ( $color->red, $color->green, $color->blue );
    my ( $y, $s ) = Twitim::NetBase::get_bw( $red, $green, $blue );

    # print "$base - $y\n";
    my $result = $y < 65 ? 'dark' : 'light';

    return $result;
}

1;
